package br.unisinos.ubicomp.locationawaretaskmanager;

import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.location.Address;
import android.location.Geocoder;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.provider.Settings;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Locale;

import com.google.android.maps.GeoPoint;

public class MainTaskManager extends FragmentActivity {
	private TextView mLatLng;
	private TextView mAddress;
	private Button mFineProviderButton;
	private Button mBothProviderButton;
	private LocationManager mLocationManager;
	private Handler mHandler;
	// private boolean mGeocoderAvailable;
	private boolean mUseFine;
	private boolean mUseBoth;

	// Keys for maintaining UI states after rotation.
	private static final String KEY_FINE = "use_fine";
	private static final String KEY_BOTH = "use_both";
	// UI handler codes.
	private static final int UPDATE_ADDRESS = 1;
	private static final int UPDATE_LATLNG = 2;

	private static final int TEN_SECONDS = 10000;
	private static final int TEN_METERS = 10;
	private static final int TWO_MINUTES = 1000 * 60 * 2;

	private List<Task> task_list;
	private TaskArrayAdapter listAdapter;
	private TaskDBHandler db;
	private static Location myCurrentLocation;

	@SuppressLint("NewApi")
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main_task_manager);

		// Restore apps state (if exists) after rotation.
		if (savedInstanceState != null) {
			mUseFine = savedInstanceState.getBoolean(KEY_FINE);
			mUseBoth = savedInstanceState.getBoolean(KEY_BOTH);
		} else {
			mUseFine = false;
			mUseBoth = false;
		}
		mLatLng = (TextView) findViewById(R.id.latlng);
		mAddress = (TextView) findViewById(R.id.address);
		// Receive location updates from the fine location provider (gps) only.
		mFineProviderButton = (Button) findViewById(R.id.provider_fine);
		// Receive location updates from both the fine (gps) and coarse
		// (network) location
		// providers.
		mBothProviderButton = (Button) findViewById(R.id.provider_both);

		// The isPresent() helper method is only available on Gingerbread or
		// above.
		// mGeocoderAvailable =
		// Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO &&
		// Geocoder.isPresent();

		// Handler for updating text fields on the UI like the lat/long and
		// address.
		mHandler = new Handler() {
			public void handleMessage(Message msg) {
				switch (msg.what) {
				case UPDATE_ADDRESS:
					mAddress.setText((String) msg.obj);
					break;
				case UPDATE_LATLNG:
					mLatLng.setText((String) msg.obj);
					break;
				}
			}
		};
		
		// Get a reference to the LocationManager object.
		mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);

		this.loadTaskList();

	}

	// Restores UI states after rotation.
	@Override
	protected void onSaveInstanceState(Bundle outState) {
		super.onSaveInstanceState(outState);
		outState.putBoolean(KEY_FINE, mUseFine);
		outState.putBoolean(KEY_BOTH, mUseBoth);
	}

	@Override
	protected void onResume() {
		super.onResume();
		setup();
	}

	@Override
	protected void onStart() {
		super.onStart();

		// Check if the GPS setting is currently enabled on the device.
		// This verification should be done during onStart() because the system
		// calls this method
		// when the user returns to the activity, which ensures the desired
		// location provider is
		// enabled each time the activity resumes from the stopped state.
		LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
		final boolean gpsEnabled = locationManager
				.isProviderEnabled(LocationManager.GPS_PROVIDER);

		if (!gpsEnabled) {
			// Build an alert dialog here that requests that the user enable
			// the location services, then when the user clicks the "OK" button,
			// call enableLocationSettings()
			new EnableGpsDialogFragment().show(getSupportFragmentManager(),
					"enableGpsDialog");
		}
	}

	// Method to launch Settings
	private void enableLocationSettings() {
		Intent settingsIntent = new Intent(
				Settings.ACTION_LOCATION_SOURCE_SETTINGS);
		startActivity(settingsIntent);
	}

	// Stop receiving location updates whenever the Activity becomes invisible.
	@Override
	protected void onStop() {
		super.onStop();
		mLocationManager.removeUpdates(listener);
	}

	// Set up fine and/or coarse location providers depending on whether the
	// fine provider or
	// both providers button is pressed.
	private void setup() {
		Location gpsLocation = null;
		Location networkLocation = null;
		mLocationManager.removeUpdates(listener);
		mLatLng.setText(R.string.unknown);
		mAddress.setText(R.string.unknown);
		// Get fine location updates only.
		if (mUseFine) {
			mFineProviderButton.setBackgroundResource(R.drawable.button_active);
			mBothProviderButton
					.setBackgroundResource(R.drawable.button_inactive);
			// Request updates from just the fine (gps) provider.
			gpsLocation = requestUpdatesFromProvider(
					LocationManager.GPS_PROVIDER, R.string.not_support_gps);
			// Update the UI immediately if a location is obtained.
			if (gpsLocation != null)
				updateUILocation(gpsLocation);
		} else if (mUseBoth) {
			// Get coarse and fine location updates.
			mFineProviderButton
					.setBackgroundResource(R.drawable.button_inactive);
			mBothProviderButton.setBackgroundResource(R.drawable.button_active);
			// Request updates from both fine (gps) and coarse (network)
			// providers.
			gpsLocation = requestUpdatesFromProvider(
					LocationManager.GPS_PROVIDER, R.string.not_support_gps);
			networkLocation = requestUpdatesFromProvider(
					LocationManager.NETWORK_PROVIDER,
					R.string.not_support_network);

			// If both providers return last known locations, compare the two
			// and use the better
			// one to update the UI. If only one provider returns a location,
			// use it.
			if (gpsLocation != null && networkLocation != null) {
				updateUILocation(getBetterLocation(gpsLocation, networkLocation));
			} else if (gpsLocation != null) {
				updateUILocation(gpsLocation);
			} else if (networkLocation != null) {
				updateUILocation(networkLocation);
			}
		}
	}

	public static Location getCurrentLocation(){
		return myCurrentLocation;
	}
	
	private void setCurrentLocation() {
		Location gpsLocation = null;
		Location networkLocation = null;

		// Get fine location updates only.
		if (mUseFine) {
			// Request updates from just the fine (gps) provider.
			gpsLocation = requestUpdatesFromProvider(
					LocationManager.GPS_PROVIDER, R.string.not_support_gps);

		} else if (mUseBoth) {
			// Get coarse and fine location updates.

			// Request updates from both fine (gps) and coarse (network)
			// providers.
			gpsLocation = requestUpdatesFromProvider(
					LocationManager.GPS_PROVIDER, R.string.not_support_gps);

			networkLocation = requestUpdatesFromProvider(
					LocationManager.NETWORK_PROVIDER,
					R.string.not_support_network);

			// If both providers return last known locations, compare the two
			// and use the better
			// one to update the UI. If only one provider returns a location,
			// use it.
			if (gpsLocation != null && networkLocation != null) {
				myCurrentLocation = getBetterLocation(gpsLocation, networkLocation);
			} else if (gpsLocation != null) {
				myCurrentLocation = gpsLocation;
			} else if (networkLocation != null) {
				myCurrentLocation = networkLocation;
			}
		}
	}
	
	private Location requestUpdatesFromProvider(final String provider,
			final int errorResId) {
		Location location = null;
		if (mLocationManager.isProviderEnabled(provider)) {
			mLocationManager.requestLocationUpdates(provider, TEN_SECONDS,
					TEN_METERS, listener);
			location = mLocationManager.getLastKnownLocation(provider);
		} else {
			Toast.makeText(this, errorResId, Toast.LENGTH_LONG).show();
		}
		return location;
	}

	// Callback method for the "fine provider" button.
	public void useFineProvider(View v) {
		mUseFine = true;
		mUseBoth = false;
		setup();
		setCurrentLocation();
	}

	// Callback method for the "both providers" button.
	public void useCoarseFineProviders(View v) {
		mUseFine = false;
		mUseBoth = true;
		setup();
		setCurrentLocation();
	}

	private void loadTaskList() {

		this.task_list = new ArrayList<Task>();

		this.db = new TaskDBHandler(this);
		
		// Create fake data for testing purpose
		this.createFakeTasks(this.db);
		this.task_list = db.getAllTasks();
		
		// Find the ListView resource.
		ListView mainListView = (ListView) findViewById(R.id.list_TaskList);

		// Create ArrayAdapter for task list.
		this.listAdapter = new TaskArrayAdapter(this, task_list);

		// Set the ArrayAdapter as the ListView's adapter.
		mainListView.setAdapter(this.listAdapter);
		
		// Defining the item click listener for listView
        OnItemClickListener itemClickListener = new OnItemClickListener() {
        	
            public void onItemClick(AdapterView<?> arg0, View view, int position, long id) {
                //Toast.makeText(getBaseContext(), "You selected :" + task_list.get(position).getName(), Toast.LENGTH_SHORT).show();
                Task task = task_list.get(position);
                
                Intent intent = new Intent(getApplicationContext(), TaskUpdater.class);
                intent.putExtra("TASK_ID",   task.getId());
                intent.putExtra("TASK_NAME", task.getName());
                intent.putExtra("TASK_LAT",  task.getLocation().getLatitudeE6());
                intent.putExtra("TASK_LON",  task.getLocation().getLongitudeE6());
                intent.putExtra("TASK_DATE", task.getDate().getTime());
        		startActivityForResult(intent, RequestCodes.TASK_UPDATE);
             }

        };
 
        // Setting the item click listener for listView
        mainListView.setOnItemClickListener(itemClickListener);
    }

	private void createFakeTasks(TaskDBHandler db) {
//		Task task;
//		GeoPoint p;
//		Date d;
//		
//		p = new GeoPoint(100, 50);
//		d = new Date(112, 11, 01, 10, 50);
//		db.addTask(new Task("Task 1", p, d));
//		//this.task_list.add(task);
//
//		p = new GeoPoint(200, 300);
//		d = new Date(112, 11, 02, 21, 50);
//		db.addTask(new Task("Task 2", p, d));
//		//this.task_list.add(task);
//
//		p = new GeoPoint(400, 500);
//		d = new Date(112, 11, 03, 11, 40);
//		db.addTask(new Task("Task 3", p, d));
//		//this.task_list.add(task);
//
//		p = new GeoPoint(600, 560);
//		d = new Date(112, 11, 04, 23, 59);
//		db.addTask(new Task("Task 4", p, d));
//		//this.task_list.add(task);
//
//		p = new GeoPoint(100, 50);
//		d = new Date(112, 11, 05, 21, 53);
//		db.addTask(new Task("Task 5", p, d));
//		//this.task_list.add(task);
		
//		this.task_list = db.getAllTasks();
	}

	// Add new task button
	public void addNewTask(View v) {
		Intent i = new Intent(getApplicationContext(), TaskCreator.class);
		startActivityForResult(i, RequestCodes.TASK);
	}

	protected void onActivityResult(int requestCode, int resultCode, Intent data) {
		super.onActivityResult(requestCode, resultCode, data);

		if (resultCode == RESULT_OK) {
			Task t;
			
			switch (requestCode) {
			case RequestCodes.TASK:

				// Task returned from task creator screen
				t = (Task) data.getSerializableExtra("task");
				this.db.addTask(t);
				this.task_list.add(t);
				
				this.listAdapter.notifyDataSetChanged();
				break;
				
			case RequestCodes.TASK_UPDATE:
				
				t = (Task) data.getSerializableExtra("TASK");
				if(t != null){
					this.task_list.remove(t);				
				}
				
				this.task_list = db.getAllTasks();
				this.listAdapter.setList(this.task_list);
				
				this.listAdapter.notifyDataSetChanged();
				break;
				
			default:
				break;
			}
		}
	}

	private void doReverseGeocoding(Location location) {
		// Since the geocoding API is synchronous and may take a while. You
		// don't want to lock
		// up the UI thread. Invoking reverse geocoding in an AsyncTask.
		(new ReverseGeocodingTask(this)).execute(new Location[] { location });
	}

	private void updateUILocation(Location location) {
		// We're sending the update to a handler which then updates the UI with
		// the new
		// location.
		Message.obtain(mHandler, UPDATE_LATLNG,
				location.getLatitude() + ", " + location.getLongitude())
				.sendToTarget();

		// Bypass reverse-geocoding only if the Geocoder service is available on
		// the device.
		// if (mGeocoderAvailable) doReverseGeocoding(location);
		doReverseGeocoding(location);
	}

	private final LocationListener listener = new LocationListener() {

		public void onLocationChanged(Location location) {
			// A new location update is received. Do something useful with it.
			// Update the UI with
			// the location update.
			updateUILocation(location);
		}

		public void onProviderDisabled(String provider) {
		}

		public void onProviderEnabled(String provider) {
		}

		public void onStatusChanged(String provider, int status, Bundle extras) {
		}
	};

	protected Location getBetterLocation(Location newLocation,
			Location currentBestLocation) {
		if (currentBestLocation == null) {
			// A new location is always better than no location
			return newLocation;
		}

		// Check whether the new location fix is newer or older
		long timeDelta = newLocation.getTime() - currentBestLocation.getTime();
		boolean isSignificantlyNewer = timeDelta > TWO_MINUTES;
		boolean isSignificantlyOlder = timeDelta < -TWO_MINUTES;
		boolean isNewer = timeDelta > 0;

		// If it's been more than two minutes since the current location, use
		// the new location
		// because the user has likely moved.
		if (isSignificantlyNewer) {
			return newLocation;
			// If the new location is more than two minutes older, it must be
			// worse
		} else if (isSignificantlyOlder) {
			return currentBestLocation;
		}

		// Check whether the new location fix is more or less accurate
		int accuracyDelta = (int) (newLocation.getAccuracy() - currentBestLocation
				.getAccuracy());
		boolean isLessAccurate = accuracyDelta > 0;
		boolean isMoreAccurate = accuracyDelta < 0;
		boolean isSignificantlyLessAccurate = accuracyDelta > 200;

		// Check if the old and new location are from the same provider
		boolean isFromSameProvider = isSameProvider(newLocation.getProvider(),
				currentBestLocation.getProvider());

		// Determine location quality using a combination of timeliness and
		// accuracy
		if (isMoreAccurate) {
			return newLocation;
		} else if (isNewer && !isLessAccurate) {
			return newLocation;
		} else if (isNewer && !isSignificantlyLessAccurate
				&& isFromSameProvider) {
			return newLocation;
		}
		return currentBestLocation;
	}

	/** Checks whether two providers are the same */
	private boolean isSameProvider(String provider1, String provider2) {
		if (provider1 == null) {
			return provider2 == null;
		}
		return provider1.equals(provider2);
	}

	// AsyncTask encapsulating the reverse-geocoding API. Since the geocoder API
	// is blocked,
	// we do not want to invoke it from the UI thread.
	private class ReverseGeocodingTask extends AsyncTask<Location, Void, Void> {
		Context mContext;

		public ReverseGeocodingTask(Context context) {
			super();
			mContext = context;
		}

		@Override
		protected Void doInBackground(Location... params) {
			Geocoder geocoder = new Geocoder(mContext, Locale.getDefault());

			Location loc = params[0];
			List<Address> addresses = null;
			try {
				addresses = geocoder.getFromLocation(loc.getLatitude(),
						loc.getLongitude(), 1);
			} catch (IOException e) {
				e.printStackTrace();
				// Update address field with the exception.
				Message.obtain(mHandler, UPDATE_ADDRESS, e.toString())
						.sendToTarget();
			}
			if (addresses != null && addresses.size() > 0) {
				Address address = addresses.get(0);
				// Format the first line of address (if available), city, and
				// country name.
				String addressText = String.format(
						"%s, %s, %s",
						address.getMaxAddressLineIndex() > 0 ? address
								.getAddressLine(0) : "", address.getLocality(),
						address.getCountryName());
				// Update address field on UI.
				Message.obtain(mHandler, UPDATE_ADDRESS, addressText)
						.sendToTarget();
			}
			return null;
		}
	}

	/**
	 * Dialog to prompt users to enable GPS on the device.
	 */
	private class EnableGpsDialogFragment extends DialogFragment {

		@Override
		public Dialog onCreateDialog(Bundle savedInstanceState) {
			return new AlertDialog.Builder(getActivity())
					.setTitle(R.string.enable_gps)
					.setMessage(R.string.enable_gps_dialog)
					.setPositiveButton(R.string.enable_gps,
							new DialogInterface.OnClickListener() {
								public void onClick(DialogInterface dialog,
										int which) {
									enableLocationSettings();
								}
							}).create();
		}
	}

}
